﻿using System;
using System.IO;

namespace Gargoyle.Roton
{
    /// <summary>
    /// The fonts supported by the FontLoader class.
    /// </summary>
    public enum FontType
    {
        Unknown,
        Bitmap,
        FontManiaCOM
    }

    /// <summary>
    /// Information that can be read from the font file.
    /// </summary>
    public struct FontData
    {
        /// <summary>
        /// The raw font data.
        /// </summary>
        public byte[] FontBitmap;

        /// <summary>
        /// The format of the file containing the desired font.
        /// </summary>
        public FontType Type;

        /// <summary>
        /// The number of lines in each glyph.
        /// </summary>
        public int Lines;

        /// <summary>
        /// Indicates whether or not the data in FontBitmap is valid and usable.
        /// </summary>
        public bool Usable;
    }
    
    public static class FontLoader
    {
        private static FileStream fs;
        private static BinaryReader br;

        private static void CloseFile()
        {
            try
            {
                br.Close();
                fs.Close();
            }
            catch { }
        }

        private static FontData GetData(FontType type)
        {
            FontData data = new FontData();
            data.Type = type;
            data.Usable = true;

            switch(type)
            {
                case FontType.Bitmap:
                    data.Lines = (int)fs.Length / 256;
                    data.FontBitmap = ReadBitmap();
                    break;
                case FontType.FontManiaCOM:
                    fs.Seek(0x05, SeekOrigin.Begin);
                    data.Lines = br.ReadByte();
                    data.FontBitmap = ReadFontManiaCOM(data.Lines);
                    break;
                default:
                    // the font file is not usable
                    data.Usable = false;
                    break;
            }

            return data;
        }

        /// <summary>
        /// Reads a font file and returns data about the font contained within, if any.
        /// </summary>
        /// <param name="Filename">The font file to be read.</param>
        /// <returns>A structure containing data about the specified font file.</returns>
        public static FontData GetFontData(string Filename)
        {
            FontData data = new FontData();

            try
            {
                OpenFile(Filename);
                data = GetData(GetFontType());
            }
            catch
            {
                // Exceptions should be handled by the calling method.
                throw;
            }
            finally
            {
                CloseFile();
            }

            return data;
        }

        /// <summary>
        /// Detects the format of the file containing the desired font.
        /// </summary>
        /// <returns>The type of font that is detected.</returns>
        public static FontType GetFontType(string Filename)
        {
            FontType type = new FontType();

            try
            {
                OpenFile(Filename);
                type = GetFontType();
            }
            catch
            {
                // Exceptions should be handled by the calling method.
                throw;
            }
            finally
            {
                CloseFile();
            }

            return type;
        }

        private static FontType GetFontType()
        {
            if(IsFontManiaCOM()) return FontType.FontManiaCOM;
            if(IsBitmap()) return FontType.Bitmap;

            return FontType.Unknown;
        }

        private static bool IsBitmap()
        {
            /*
             * This simply checks to see if the file is evenly divisible by 256.
             */
            return fs.Length % 256 == 0;
        }

        private static bool IsFontManiaCOM()
        {
            /*
             * Check for a FontMania COM file.
             */

            // check for short JMP in the first byte
            fs.Seek(0x00, SeekOrigin.Begin);
            if(br.ReadByte() != 0xEB)
                return false;

            // check for a valid character height (8, 14, and 16 are supported)
            fs.Seek(0x05, SeekOrigin.Begin);
            switch(br.ReadByte())
            {
                case 8: case 14: case 16:
                    break;
                default:
                    return false;
            }

            // check for FM magic
            fs.Seek(0x29f, SeekOrigin.Begin);
            if(br.ReadUInt16() != 19782)
                return false;

            // looks good!
            return true;
        }

        private static void OpenFile(string Filename)
        {
            try
            {
                fs = new FileStream(Filename, FileMode.Open, FileAccess.Read, FileShare.Read);
                br = new BinaryReader(fs);
            }
            catch
            {
                throw;
            }
        }

        private static byte[] ReadBitmap()
        {
            byte[] data = new byte[fs.Length];

            // read and return the entire file
            fs.Seek(0x00, SeekOrigin.Begin);
            data = br.ReadBytes((int)fs.Length);
            return data;
        }

        private static byte[] ReadFontManiaCOM(int Lines)
        {
            int dataSize = Lines * 256;
            byte[] data = new byte[dataSize];

            // FontMania font data starts at 0x5A2 and ends at Length - 1 byte.
            fs.Seek(0x5a2, SeekOrigin.Begin);
            data = br.ReadBytes(dataSize);
            return data;
        }
    }
}